﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Mendel
{
    public class BuiltInWords
    {
        public static void AddAll()
        {
            new Item(".", new Word(print));
            new Item("+", new Word(add));
            new Item("[", new Word(liststart));
            new Item("]", new Word(listend));
            new Item("-", new Word(substract));
            new Item("*", new Word(multiply));
            new Item("/", new Word(divide));
            new Item("type", new Word(type));
            new Item("==", new Word(equals));
            new Item("IF", new Word(If, false, true));
            new Item("THEN", new Word(Then));
            new Item("ELSE", new Word(Else, true, true));
            new Item("END", new Word(End, true, true));
            new Item("universe", new Word(universe));
            new Item("var", new Word(Var));
            new Item("=", new Word(setval));
            new Item("spill", new Word(spill));
            new Item("WHILE", new Word(While, true, true));
            new Item("LOOP", new Word(Loop, true, true));
        }

        static bool print(Processor proc)
        {
            Item tmp = proc.DataStack.Pop();

            proc.Output += tmp.ST + " ";
            
            return true;
        }

        static bool add(Processor proc)
        {
            Item[] tmp=new Item[] { proc.DataStack.Pop(), proc.DataStack.Pop() };

            if (tmp.Count(p => p.Type == DType.Reference) > 0)
            {
                for(int i=0;i<=1;i++)
                {
                    if (tmp[i].Type == DType.Reference)
                        tmp[i] = tmp[i].RE;
                }
            }

            if (tmp.Count(p=>p.Type==DType.Word)>0 || tmp.Count(p=>p.Type==DType.List)>0)
            {
                List<Item> tmp2 = tmp[0].LI;
                List<Item> ret = new List<Item>(tmp[1].LI);
                foreach (var item in tmp2)
                    ret.Add(item);
                
                proc.DataStack.Push(new Item(new List(ret)));
            }
            else if (tmp.Count(p => p.Type == DType.String) > 0)
            {
                proc.DataStack.Push(new Item(new String(tmp[1].ST + tmp[0].ST)));
            }
            else if (tmp.Count(p => p.Type == DType.Float) > 0)
            {
                proc.DataStack.Push(new Item(new Float(tmp[0].FL + tmp[1].FL)));
            }
            else
            {
                proc.DataStack.Push(new Item(new Integer(tmp[0].IN + tmp[1].IN)));
            }

            return true;
        }

        static bool liststart(Processor proc)
        {
            proc.ControlStack.Push(new Item(new Integer(proc.DataStack.Count), RType.DataStackDepth));
            return true;
        }

        static bool listend(Processor proc)
        {
            try
            {
                Item tmp=proc.ControlStack.Last(p => p.Role == RType.DataStackDepth);
                int limit = proc.DataStack.Count - tmp.IN;
                proc.ControlStack.Remove(tmp);
                List<Item> ret = new List<Item>(proc.DataStack.Reverse().Take(limit));
                for (int i = 0; i < limit; i++)
                    proc.DataStack.RemoveLast();
                ret.Reverse();
                proc.DataStack.Push(new Item(new List((ret))));
                return true;
            }
            catch (InvalidOperationException)
            {
                Universe.AddError(new Exception("Hiányzik a DataStackDepth control érték!"));
                return true;
            }
            catch (Exception)
            {
                throw;
            }
        }

        static bool substract(Processor proc)
        {
            Item[] tmp = new Item[] { proc.DataStack.Pop(), proc.DataStack.Pop() };

            if (tmp.Count(p => p.Type == DType.Integer) == 2)
            {
                proc.DataStack.Push(new Item(new Integer(tmp[1].IN - tmp[0].IN)));
            }
            else
            {
                proc.DataStack.Push(new Item(new Float(tmp[1].FL - tmp[0].FL)));
            }

            return true;
        }

        static bool multiply(Processor proc)
        {
            Item[] tmp = new Item[] { proc.DataStack.Pop(), proc.DataStack.Pop() };

            if (tmp.Count(p => p.Type == DType.Integer) == 2)
            {
                proc.DataStack.Push(new Item(new Integer(tmp[1].IN * tmp[0].IN)));
            }
            else
            {
                proc.DataStack.Push(new Item(new Float(tmp[1].FL * tmp[0].FL)));
            }

            return true;
        }

        static bool divide(Processor proc)
        {
            Item[] tmp = new Item[] { proc.DataStack.Pop(), proc.DataStack.Pop() };

            proc.DataStack.Push(new Item(new Float(tmp[1].FL / tmp[0].FL)));
       
            return true;
        }

        static bool type(Processor proc)
        {
            Item tmp = proc.DataStack.Pop();
           
            proc.DataStack.Push(new Item(new String(tmp.Type.ToString())));

            return true;
        }

        private static bool eq_helper(Item[] tmp)
        {
            if (tmp.Count(p => p.Type == DType.Reference) > 0)
            {
                for (int i = 0; i <= 1; i++)
                {
                    if (tmp[i].Type == DType.Reference)
                        tmp[i] = tmp[i].RE;
                }
            }

            if (tmp.Count(p=>p.Type==DType.Word)>0 || tmp.Count(p=>p.Type==DType.List)>0)
            {
                bool ret=true;
                for(int i=0;i<tmp[0].LI.Count && i<tmp[1].LI.Count;i++)
                    if(!eq_helper(new Item[] {tmp[0].LI[i], tmp[1].LI[i]}))
                    {
                        ret=false;
                        break;
                    }
                return ret;
            }
            else if (tmp.Count(p => p.Type == DType.String) > 0)
            {
                return tmp[0].ST.Equals(tmp[1].ST);
            }
            else if (tmp.Count(p => p.Type == DType.Float) > 0)
            {
                return tmp[0].FL==tmp[1].FL;
            }
            else
            {
                return tmp[0].IN==tmp[1].IN;
            }
        }

        static bool equals(Processor proc)
        {
            Item[] tmp=new Item[] { proc.DataStack.Pop(), proc.DataStack.Pop() };

            proc.DataStack.Push(new Item(new Integer(eq_helper(tmp)?1:0)));

            return true;
        }

        static bool If(Processor proc)
        {
            proc.ControlStack.Push(new Item(new Integer(1), RType.SkipSignal));

            return true;
        }

        static bool Then(Processor proc)
        {
            if (proc.DataStack.Pop().IN == 0)
                proc.RunMode = RMode.Skip;

            return true;
        }

        static bool Else(Processor proc)
        {
            if (proc.ControlStack.Count(p => p.Role == RType.SkipSignal) == 0)      // ha nem kell az egész if szerkezetet skip-elni
            {
                if (proc.RunMode == RMode.Skip)
                    proc.RunMode = RMode.Normal;
                else
                    proc.RunMode = RMode.Skip;
            }

            return true;
        }

        static bool End(Processor proc)
        {
            if (proc.ControlStack.Count(p => p.Role == RType.SkipSignal) > 0)
            {
                Item tmp = proc.ControlStack.Last(p => p.Role == RType.SkipSignal);         
                proc.ControlStack.Remove(tmp);
            }
            else if(proc.RunMode==RMode.Skip)
                proc.RunMode = RMode.Normal;

            return true;
        }

        static bool universe(Processor proc)
        {
            proc.DataStack.Push(new Item(new List(Universe.GetUniverse())));

            return true;
        }

        static bool Var(Processor proc)
        {
            Item tmp = proc.DataStack.Pop();
            if (!tmp.Constant)
                return true;
            string name = proc.DataStack.Pop().ST;

            tmp.Constant = false;
            tmp.Name = name;

            Universe.AddItem(tmp);

            return true;
        }

        static bool setval(Processor proc)
        {
            Item newval = proc.DataStack.Pop();
            Item change = proc.DataStack.Pop();

            change.DataDirectly = newval.DataDirectly;

            return true;
        }

        static bool spill(Processor proc)
        {
            List<Item> list = proc.DataStack.Pop().LI;
            foreach (var item in list)
            {
                proc.DataStack.Push(item);
            }

            return true;
        }

        static bool While(Processor proc)
        {
            if (proc.RunMode == RMode.Skip)
                proc.ControlStack.Push(new Item(new Integer(1), RType.SkipSignal));
            else
                proc.ControlStack.Push(new Item(new Integer(proc.ReturnStack.Last().WO.PC), RType.JumpTo));

            return true;
        }

        static bool Loop(Processor proc)
        {
            if (proc.ControlStack.Count(p => p.Role == RType.SkipSignal) > 0)
            {
                Item tmp = proc.ControlStack.Last(p => p.Role == RType.SkipSignal);
                proc.ControlStack.Remove(tmp);
            }
            else if (proc.RunMode == RMode.Normal)
            {
                try
                {
                    Item tmp = proc.ControlStack.Last(p => p.Role == RType.JumpTo);
                    int pc = tmp.IN;

                    proc.ReturnStack.Last().WO.PC=pc;
                }
                catch(InvalidOperationException)
                {
                    Universe.AddError(new Exception("Hiányzó JumpTo control adat!"));
                }
            }
            else if (proc.RunMode == RMode.Skip)
            {
                proc.RunMode = RMode.Normal;
                try
                {
                    Item tmp = proc.ControlStack.Last(p => p.Role == RType.JumpTo);
                    proc.ControlStack.Remove(tmp);
                }
                catch (InvalidOperationException)
                {
                    Universe.AddError(new Exception("Hiányzó JumpTo control adat!"));
                }
            }

            return true;
        }
    }
}